package io.flutter.plugins.camera;

import android.app.Activity;
import android.hardware.camera2.CameraAccessException;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import io.flutter.plugin.common.BinaryMessenger;
import io.flutter.plugin.common.EventChannel;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
import io.flutter.plugin.common.MethodChannel.Result;
import io.flutter.plugins.camera.CameraPermissions.PermissionsRegistry;
import io.flutter.view.TextureRegistry;

final class MethodCallHandlerImpl implements MethodChannel.MethodCallHandler {
    private final Activity activity;
    private final BinaryMessenger messenger;
    private final CameraPermissions cameraPermissions;
    private final PermissionsRegistry permissionsRegistry;
    private final TextureRegistry textureRegistry;
    private final MethodChannel methodChannel;
    private final EventChannel imageStreamChannel;
    private @Nullable
    Camera camera;

    MethodCallHandlerImpl(
            Activity activity,
            BinaryMessenger messenger,
            CameraPermissions cameraPermissions,
            PermissionsRegistry permissionsAdder,
            TextureRegistry textureRegistry) {
        this.activity = activity;
        this.messenger = messenger;
        this.cameraPermissions = cameraPermissions;
        this.permissionsRegistry = permissionsAdder;
        this.textureRegistry = textureRegistry;

        methodChannel = new MethodChannel(messenger, "plugins.flutter.io/camera");
        imageStreamChannel = new EventChannel(messenger, "plugins.flutter.io/camera/imageStream");
        methodChannel.setMethodCallHandler(this);
    }

    @Override
    public void onMethodCall(@NonNull MethodCall call, @NonNull final Result result) {
        switch (call.method) {
            case "availableCameras":
                try {
                    result.success(CameraUtils.getAvailableCameras(activity));
                } catch (Exception e) {
                    handleException(e, result);
                }
                break;
            case "availableResolutions":
                try {
                    String cameraName = call.argument("cameraName");
                    result.success(CameraUtils.getAvailableResolutions(activity, cameraName));
                } catch (Exception e) {
                    handleException(e, result);
                }
                break;
            case "initialize": {
                if (camera != null) {
                    camera.close();
                }
                cameraPermissions.requestPermissions(
                        activity,
                        permissionsRegistry,
                        call.argument("enableAudio"),
                        (String errCode, String errDesc) -> {
                            if (errCode == null) {
                                try {
                                    instantiateCamera(call, result);
                                } catch (Exception e) {
                                    handleException(e, result);
                                }
                            } else {
                                result.error(errCode, errDesc, null);
                            }
                        });

                break;
            }
            case "takePicture": {
                camera.takePicture(call.argument("path"), result);
                break;
            }
            case "prepareForVideoRecording": {
                // This optimization is not required for Android.
                result.success(null);
                break;
            }
            case "startVideoRecording": {
                camera.startVideoRecording(call.argument("filePath"), result);
                break;
            }
            case "stopVideoRecording": {
                camera.stopVideoRecording(result);
                break;
            }
            case "pauseVideoRecording": {
                camera.pauseVideoRecording(result);
                break;
            }
            case "resumeVideoRecording": {
                camera.resumeVideoRecording(result);
                break;
            }
            case "startImageStream": {
                try {
                    camera.startPreviewWithImageStream(imageStreamChannel);
                    result.success(null);
                } catch (Exception e) {
                    handleException(e, result);
                }
                break;
            }
            case "stopImageStream": {
                try {
                    camera.startPreview();
                    result.success(null);
                } catch (Exception e) {
                    handleException(e, result);
                }
                break;
            }
            case "dispose": {
                if (camera != null) {
                    camera.dispose();
                }
                result.success(null);
                break;
            }
            default:
                result.notImplemented();
                break;
        }
    }

    void stopListening() {
        methodChannel.setMethodCallHandler(null);
    }

    private void instantiateCamera(MethodCall call, Result result) throws CameraAccessException {
        String cameraName = call.argument("cameraName");
        String resolutionPreset = call.argument("resolutionPreset");
        boolean enableAudio = call.argument("enableAudio");
        TextureRegistry.SurfaceTextureEntry flutterSurfaceTexture =
                textureRegistry.createSurfaceTexture();
        DartMessenger dartMessenger = new DartMessenger(messenger, flutterSurfaceTexture.id());
        camera =
                new Camera(
                        activity,
                        flutterSurfaceTexture,
                        dartMessenger,
                        cameraName,
                        resolutionPreset,
                        enableAudio);

        camera.open(result);
    }

    // We move catching CameraAccessException out of onMethodCall because it causes a crash
    // on plugin registration for sdks incompatible with Camera2 (< 21). We want this plugin to
    // to be able to compile with <21 sdks for apps that want the camera and support earlier version.
    @SuppressWarnings("ConstantConditions")
    private void handleException(Exception exception, Result result) {
        if (exception instanceof CameraAccessException) {
            result.error("CameraAccess", exception.getMessage(), null);
        }

        throw (RuntimeException) exception;
    }
}
